/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <bamp/exception.hpp>
#include <bamp/noncopyable.hpp>

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>

#include <map>
#include <set>
#include <string>

#include "dynamic_lib.hpp"
#include "plugin_loader.hpp"

namespace bamp
{
    // Convenience internal container typedefs
    typedef boost::filesystem::path Path;

    typedef std::map <Path, DynamicLibPtr> LibraryMap;
    typedef std::pair<Path, DynamicLibPtr> LibraryPair;

    typedef std::pair<std::string, std::string>  ClassTypePair;
    typedef std::pair<unsigned int, Path> PrioPathPair;
    typedef std::set <PrioPathPair>  PluginSet;
    typedef std::map <ClassTypePair, PluginSet> TypeMap;
    typedef std::pair<const ClassTypePair, PluginSet> TypePair;
}

// Hidden implementation.
struct bamp::PluginLoader::Implementation
{
    // Path => dynamic library mapping for all plugins.
    LibraryMap m_libs;

    // Tree used to find plugins by class name, type and priority.
    TypeMap m_types;

    // Mutex for access to both containers.
    boost::mutex m_mutex;
};


bamp::PluginLoader::PluginLoader()
    : pimpl(new Implementation())
{
    // Nothing here
}


bamp::PluginLoader::~PluginLoader()
{
    // Nothing here
}


void bamp::PluginLoader::load(const Path& path, int priority)
{
    boost::mutex::scoped_lock lock(pimpl->m_mutex);

    // Don't load a plugin twice
    typename LibraryMap::const_iterator it = pimpl->m_libs.find(path);
    if (it != pimpl->m_libs.end())
    {
        return;
    }
    
    // Load the plugin
    DynamicLibPtr lib(new DynamicLib(path));

    // Resolve the "class_list" symbol. It is a null-terminated list
    // of interfaces implemented by the plugin.
    const char** classList = lib->resolve<const char**>("class_list");

    // For each class in "class_list", resolve the *_types symbol. It
    // is a null-terminated list of data types that can be handled by
    // the plugin.
    for (size_t i = 0; classList[i]; ++i)
    {
        const std::string cls(classList[i]);
        const char** types = lib->resolve<const char**>(cls + "_types");
        for (size_t j = 0; types[j]; ++j)
        {
            const std::string type = types[j];

            // Insert the plugin into the lookup tree.
            const PrioPathPair p(priority, path);
            pimpl->m_types[ClassTypePair(cls, type)].insert(p);
        }
    }

    // Set the plugin's path => library mapping
    pimpl->m_libs.insert(LibraryPair(path, lib));
}


void bamp::PluginLoader::unload(const Path& path)
{
    boost::mutex::scoped_lock lock(pimpl->m_mutex);

    // Remove the plugin from the map. When all instances are
    // released, it will be automatically unloaded.
    pimpl->m_libs.erase(path);

    // Remove all type mappings for the plugin
    BOOST_FOREACH(TypePair& t, pimpl->m_types)
    {
        BOOST_FOREACH(const PrioPathPair& p, t.second)
        {
            if (p.second == path)
                t.second.erase(p);
        }
    }
}


void* bamp::PluginLoader::_create(const Path& path,
                                  const std::string& classname) const
{
    boost::mutex::scoped_lock lock(pimpl->m_mutex);

    // Find the plugin
    typename LibraryMap::const_iterator it = pimpl->m_libs.find(path);
    BAMP_ASSERT_THROW(it != pimpl->m_libs.end(), NOT_FOUND);

    // Resolve the create() function
    typedef void*(*CreateFunc)();
    const std::string fname = "create_" + classname;
    CreateFunc ctor = it->second->resolve<CreateFunc>(fname);

    // Create a new instance of the plugin and return it
    void* ctx = ctor();
    return ctx;
}


bamp::PluginLoader::Path bamp::PluginLoader::_find
(const std::string& type, const std::string& classname) const
{
    boost::mutex::scoped_lock lock(pimpl->m_mutex);

    // Find the plugin with the lowest priority value that supports
    // the type and that implements the requested class' interface.
    const ClassTypePair p(classname, type);
    TypeMap::const_iterator it = pimpl->m_types.find(p);
    BAMP_ASSERT_THROW(it != pimpl->m_types.end(), NOT_FOUND);
    BAMP_ASSERT_THROW(it->second.size() > 0, NOT_FOUND);
    return it->second.begin()->second;
}
